﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.ComponentModel;
using System.Drawing.Drawing2D;

namespace Djs.Common.Components
{
    /// <summary>
    /// GInteractiveControl : Control with mouse event support and active area (and with buffered graphic too)
    /// </summary>
    public class GInteractiveControl : GControlLayered
    {
        #region Constructor
        public GInteractiveControl()
        {
            this.Init();
        }
        #endregion
        #region Interactive area
        private void Init()
        {
            this._ItemsList = new List<IInteractiveItem>();
            this.SetStyle(ControlStyles.Selectable | ControlStyles.UserMouse, true);
            this._StopWatchInit();
            this._ToolTipInit();
            this._DrawInit();
            this._MouseEventInit();
        }
        protected List<IInteractiveItem> ItemsList
        {
            get
            {
                if (this._ItemsList == null)
                    this._ItemsList = new List<IInteractiveItem>();
                return this._ItemsList;
            }
        }
        private List<IInteractiveItem> _ItemsList;
        #endregion
        #region Mouse handlers: primary handlers, interactive handlers
        private void _MouseEventInit()
        {
            this._CurrentItem = null;
            this.MouseEnter += new EventHandler(_MouseEnter);
            this.MouseMove += new MouseEventHandler(_MouseMove);
            this.MouseDown += new MouseEventHandler(_MouseDown);
            this.MouseUp += new MouseEventHandler(_MouseUp);
            this.MouseWheel += new MouseEventHandler(_MouseWheel);
            this.MouseLeave += new EventHandler(_MouseLeave);
            this._DragStartSize = SystemInformation.DragSize;
        }
        #region Mouse primary event handlers (called from Win32).
        void _MouseEnter(object sender, EventArgs e)
        {
            try
            {
                this._MouseDrawInit(null);
                this._MouseAllReset();
            }
            finally
            {
                this._MouseDrawRun();
            }
        }
        void _MouseMove(object sender, MouseEventArgs e)
        {
            try
            {
                this._MouseDrawInit(e);
                if (!this._CurrentMouseDownLocation.HasValue && (e.Button == System.Windows.Forms.MouseButtons.Left || e.Button == System.Windows.Forms.MouseButtons.Right))
                    this._MouseFell(e);

                if (!this._CurrentMouseDownLocation.HasValue)
                    this._MouseOver(e);
                else
                {
                    if (this._CurrentMouseDragStart.HasValue && !this._CurrentMouseDragStart.Value.Contains(e.Location))
                        this._MouseDragBegin(e);
                    if (!this._CurrentMouseDragStart.HasValue)
                        this._MouseDragMove(e);
                }
            }
            finally
            {
                this._MouseDrawRun();
            }
        }
        void _MouseDown(object sender, MouseEventArgs e)
        {
            try
            {
                this._MouseDrawInit(e);
                this._MouseFell(e);
            }
            finally
            {
                this._MouseDrawRun();
            }
        }
        void _MouseUp(object sender, MouseEventArgs e)
        {
            try
            {
                this._MouseDrawInit(e);
                if (this._CurrentMouseDragOffset.HasValue)
                    this._MouseDragEnd(e);
                else
                    this._MouseRaise(e);
            }
            finally
            {
                this._MouseDrawRun();
            }
        }
        void _MouseWheel(object sender, MouseEventArgs e)
        {
            try
            {
                this._MouseDrawInit(e);
                this._MouseOneWheel(e);
            }
            finally
            {
                this._MouseDrawRun();
            }
        }
        void _MouseLeave(object sender, EventArgs e)
        {
            try
            {
                this._MouseDrawInit(null);
                this._MouseOver(null);
                this._MouseAllReset();
                this._MouseDrawRun();
            }
            finally
            {
                this._MouseDrawRun();
            }
        }
        #endregion
        #region Mouse interactive item handlers
        private void _MouseOver(MouseEventArgs e)
        {
            if (e != null)
            {
                GCurrentItem gci = this.FindItemAtPoint(e.Location, true);
                this._ItemMouseExchange(this._CurrentItem, gci, this._CurrentMouseRelativeLocation);
            }
            else
            {
                this._CurrentMouseRelativeLocation = null;
                this._ItemMouseExchange(this._CurrentItem, null, null);
                this._ActivateCursor(SysCursorType.Default);
            }
        }
        private void _MouseFell(MouseEventArgs e)
        {
            GCurrentItem gci = this.FindItemAtPoint(e.Location);
            this._ItemMouseExchange(this._CurrentItem, gci, this._CurrentMouseRelativeLocation);
            this._CurrentMouseDownLocation = e.Location;
            this._CurrentMouseDownButtons = e.Button;
            this._CurrentMouseDragStart = e.Location.CreateRectangleFromCenter(this._DragStartSize);
            this._CurrentMouseDragOffset = null;
            this._ItemMouseCallEvent(this._CurrentItem, GInteractiveChangeState.LeftDown, this._CurrentMouseRelativeLocation, null);
        }
        private void _MouseRaise(MouseEventArgs e)
        {
            if (this._CurrentItem != null)
            {
                this._CurrentMouseRelativeLocation = this._GetRelativePointToCurrentItem(e.Location);
                this._ItemMouseCallEvent(this._CurrentItem, GInteractiveChangeState.LeftUp, this._CurrentMouseRelativeLocation, null);
                if (this._CurrentItem.CanDoubleClick && GCurrentItem.IsDoubleClick(this._LastClickItem, this._CurrentItem))
                {
                    this._ItemMouseCallEvent(this._CurrentItem, GInteractiveChangeState.LeftDoubleClick, this._CurrentMouseRelativeLocation, null);
                }
                else if (this._CurrentItem.CanClick)
                {
                    this._ItemMouseCallEvent(this._CurrentItem, GInteractiveChangeState.LeftClick, this._CurrentMouseRelativeLocation, null);
                }
                this._LastClickItem = this._CurrentItem;
            }
            this._MouseDownReset();
        }
        private void _MouseDragBegin(MouseEventArgs e)
        {
            this._CurrentMouseRelativeLocation = _GetRelativePoint(this._CurrentMouseDownLocation.Value, this._CurrentItem);    // Relative position of mouse on MouseDown, not current position (after mouse moved out from _CurrentMouseDragStart)
            if (this._CurrentItem.CanDrag)
            {
                Point? userDragPoint = null;
                this._ItemMouseCallEvent(this._CurrentItem, GInteractiveChangeState.LeftDragBegin, this._CurrentMouseRelativeLocation, null, ref userDragPoint);
                if (userDragPoint.HasValue)
                    this._UserDragPointOffset = userDragPoint.Value.Sub(this._CurrentMouseDownLocation.Value);
            }
            this._CurrentMouseDragOffset = this._GetRelativePointToCurrentItem(this._CurrentMouseDownLocation.Value);
            this._CurrentMouseDragStart = null;
        }
        private void _MouseDragMove(MouseEventArgs e)
        {
            if (this._CurrentItem.CanDrag)
            {
                Point? userDragPoint = null;
                if (this._UserDragPointOffset.HasValue)
                    userDragPoint = e.Location.Add(this._UserDragPointOffset.Value);
                this._CurrentMouseRelativeLocation = _GetRelativePoint(e.Location, this._CurrentItem);
                Rectangle currentArea = this._CurrentItem.CurrentItem.ActiveBounds;
                Point location = e.Location.Sub(this._CurrentMouseDragOffset.Value);
                Rectangle? dragToBounds = new Rectangle(location, currentArea.Size);
                this._ItemMouseCallEvent(this._CurrentItem, GInteractiveChangeState.LeftDragMove, this._CurrentMouseRelativeLocation, dragToBounds, ref userDragPoint);
            }
        }
        private void _MouseDragEnd(MouseEventArgs e)
        {
            this._CurrentMouseRelativeLocation = _GetRelativePoint(e.Location, this._CurrentItem);
            this._ItemMouseCallEvent(this._CurrentItem, GInteractiveChangeState.LeftUp, this._CurrentMouseRelativeLocation, null);
            if (this._CurrentItem.CanDrag)
                this._ItemMouseCallEvent(this._CurrentItem, GInteractiveChangeState.LeftDragEnd, this._CurrentMouseRelativeLocation, null);
            this._MouseDownReset();
        }
        private void _MouseOneWheel(MouseEventArgs e)
        {
            GCurrentItem gci = this.FindItemAtPoint(e.Location);
            this._ItemMouseExchange(this._CurrentItem, gci, this._CurrentMouseRelativeLocation);
            GInteractiveChangeState change = (e.Delta > 0 ? GInteractiveChangeState.WheelUp : GInteractiveChangeState.WheelDown);
            this._ItemMouseCallEvent(this._CurrentItem, change, this._CurrentMouseRelativeLocation, null);
        }
        /// <summary>
        /// Return change state for current mousebutton (_CurrentMouseDownButtons), for change specified for left button.
        /// When no button pressed, or state is not button-dependent, then unchanged state is returned.
        /// </summary>
        /// <param name="state"></param>
        /// <returns></returns>
        private GInteractiveChangeState _GetStateForCurrentButton(GInteractiveChangeState state, bool isEnabled)
        {
            if (state == GInteractiveChangeState.MouseOver)
            {
                return (isEnabled ? GInteractiveChangeState.MouseOver : GInteractiveChangeState.MouseOverDisabled);
            }

            if (this._CurrentMouseDownButtons.HasValue)
            {
                bool right = (this._CurrentMouseDownButtons.Value == System.Windows.Forms.MouseButtons.Right);
                switch (state)
                {
                    case GInteractiveChangeState.LeftDown:
                    case GInteractiveChangeState.RightDown:
                        return (right ? GInteractiveChangeState.RightDown : GInteractiveChangeState.LeftDown);
                    case GInteractiveChangeState.LeftDragBegin:
                    case GInteractiveChangeState.RightDragBegin:
                        return (right ? GInteractiveChangeState.RightDragBegin : GInteractiveChangeState.LeftDragBegin);
                    case GInteractiveChangeState.LeftDragMove:
                    case GInteractiveChangeState.RightDragMove:
                        return (right ? GInteractiveChangeState.RightDragMove : GInteractiveChangeState.LeftDragMove);
                    case GInteractiveChangeState.LeftDragEnd:
                    case GInteractiveChangeState.RightDragEnd:
                        return (right ? GInteractiveChangeState.RightDragEnd : GInteractiveChangeState.LeftDragEnd);
                    case GInteractiveChangeState.LeftUp:
                    case GInteractiveChangeState.RightUp:
                        return (right ? GInteractiveChangeState.RightUp : GInteractiveChangeState.LeftUp);
                    case GInteractiveChangeState.LeftClick:
                    case GInteractiveChangeState.RightClick:
                        return (right ? GInteractiveChangeState.RightClick : GInteractiveChangeState.LeftClick);
                    case GInteractiveChangeState.LeftDoubleClick:
                    case GInteractiveChangeState.RightDoubleClick:
                        return (right ? GInteractiveChangeState.RightDoubleClick : GInteractiveChangeState.LeftDoubleClick);
                }
            }
            return state;
        }
        /// <summary>
        /// Return a state (static) after specified change (dynamic event)
        /// </summary>
        /// <param name="change"></param>
        /// <returns></returns>
        private GInteractiveState _GetStateAfterChange(GInteractiveChangeState change)
        {
            switch (change)
            {
                case GInteractiveChangeState.None: return GInteractiveState.None;
                case GInteractiveChangeState.MouseEnter: return GInteractiveState.Mouse;
                case GInteractiveChangeState.MouseOver: return GInteractiveState.Mouse;
                case GInteractiveChangeState.MouseOverDisabled: return GInteractiveState.Mouse;
                case GInteractiveChangeState.MouseLeave: return GInteractiveState.None;
                case GInteractiveChangeState.MouseEnterSubItem: return GInteractiveState.Mouse;
                case GInteractiveChangeState.MouseLeaveSubItem: return GInteractiveState.Mouse;
                case GInteractiveChangeState.LeftDown: return GInteractiveState.LeftDown;
                case GInteractiveChangeState.LeftDragBegin: return GInteractiveState.LeftDrag;
                case GInteractiveChangeState.LeftDragMove: return GInteractiveState.LeftDrag;
                case GInteractiveChangeState.LeftDragEnd: return GInteractiveState.Mouse;
                case GInteractiveChangeState.LeftUp: return GInteractiveState.Mouse;
                case GInteractiveChangeState.LeftClick: return GInteractiveState.Mouse;
                case GInteractiveChangeState.LeftDoubleClick: return GInteractiveState.Mouse;
                case GInteractiveChangeState.RightDown: return GInteractiveState.RightDown;
                case GInteractiveChangeState.RightDragBegin: return GInteractiveState.RightDrag;
                case GInteractiveChangeState.RightDragMove: return GInteractiveState.RightDrag;
                case GInteractiveChangeState.RightDragEnd: return GInteractiveState.Mouse;
                case GInteractiveChangeState.RightUp: return GInteractiveState.Mouse;
                case GInteractiveChangeState.RightClick: return GInteractiveState.Mouse;
                case GInteractiveChangeState.RightDoubleClick: return GInteractiveState.Mouse;
                case GInteractiveChangeState.WheelUp: return GInteractiveState.Mouse;
                case GInteractiveChangeState.WheelDown: return GInteractiveState.Mouse;
            }
            return GInteractiveState.None;
        }
        /// <summary>
        /// Call events MouseLeave and MouseEnter when neccessary
        /// </summary>
        /// <param name="prev"></param>
        /// <param name="next"></param>
        private void _ItemMouseExchange(GCurrentItem prev, GCurrentItem next)
        {
            this._ItemMouseExchange(prev, next, null);
        }
        /// <summary>
        /// Call events MouseLeave and MouseEnter when neccessary
        /// </summary>
        /// <param name="prev"></param>
        /// <param name="next"></param>
        private void _ItemMouseExchange(GCurrentItem prev, GCurrentItem next, Point? mouseRelativePoint)
        {
            List<IInteractiveItem> leaveList, enterList;
            IInteractiveItem leaveSubItem, enterSubItem;
            GCurrentItem.MapExchange(prev, next, out leaveList, out leaveSubItem, out enterList, out enterSubItem);

            if (leaveSubItem != null)
                this._ItemMouseCallEvent(prev, leaveSubItem, prev.CurrentSubItem, GInteractiveChangeState.MouseLeaveSubItem);

            foreach (IInteractiveItem item in leaveList)
                this._ItemMouseCallEvent(prev, item, null, GInteractiveChangeState.MouseLeave);

            foreach (IInteractiveItem item in enterList)
                this._ItemMouseCallEvent(next, item, null, GInteractiveChangeState.MouseEnter);

            if (enterSubItem != null)
                this._ItemMouseCallEvent(next, enterSubItem, next.CurrentSubItem, GInteractiveChangeState.MouseEnterSubItem);

            if (next != null && next.CanOver)
                this._ItemMouseCallEvent(next, GInteractiveChangeState.MouseOver, mouseRelativePoint, null);

            this._CurrentItem = next;
        }
        /// <summary>
        /// Call interactive event for specified item and change.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="state"></param>
        private void _ItemMouseCallEvent(GCurrentItem item, GInteractiveChangeState change, Point? mouseRelativePoint, Rectangle? dragToArea)
        {
            Point? userDragPoint = null;
            this._ItemMouseCallEvent(item, change, mouseRelativePoint, dragToArea, ref userDragPoint);
        }
        /// <summary>
        /// Call interactive event for specified item and change.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="change">Change of state, independently on MouseButton (i.e. LeftDown, in situation where is pressed Right Mouse button). Real change state is detected in this method, with _GetStateForCurrentButton() method.</param>
        private void _ItemMouseCallEvent(GCurrentItem item, GInteractiveChangeState change, Point? mouseRelativePoint, Rectangle? dragToArea, ref Point? userDragPoint)
        {
            GInteractiveChangeState realChange = this._GetStateForCurrentButton(change, item.IsEnabled);
            GInteractiveChangeStateArgs e = new GInteractiveChangeStateArgs(item.ExistsItem, item.CurrentItem, item.CurrentSubItem, realChange, _GetStateAfterChange(realChange), mouseRelativePoint, dragToArea);
            e.UserDragPoint = userDragPoint;

            if (item.ExistsItem)
                item.CurrentItem.StateChanged(e);

            this._OnInteractiveStateChanged(e);

            this._MouseDrawStore(e);

            userDragPoint = e.UserDragPoint;
        }
        private void _ItemMouseCallEvent(GCurrentItem gci, IInteractiveItem item, IInteractiveSubItem subItem, GInteractiveChangeState change)
        {
            GInteractiveChangeState realChange = this._GetStateForCurrentButton(change, item.IsEnabled);
            GInteractiveChangeStateArgs e = new GInteractiveChangeStateArgs(true, item, subItem, realChange, _GetStateAfterChange(realChange), null, null);
            e.UserDragPoint = null;

            item.StateChanged(e);

            this._OnInteractiveStateChanged(e);

            this._MouseDrawStore(e);
        }
        /// <summary>
        /// Reset all mouse variables.
        /// Call this method after MouseLeave.
        /// </summary>
        private void _MouseAllReset()
        {
            this._MouseDownReset();
            this._CurrentItem = null;
            this._CurrentMouseRelativeLocation = null;
            this._CurrentCursorType = null;
        }
        /// <summary>
        /// Reset mouse variables used in MouseDown state.
        /// Call this method after MouseUp, Click, DragEnd.
        /// </summary>
        private void _MouseDownReset()
        {
            this._CurrentMouseDownLocation = null;
            this._CurrentMouseDownButtons = null;
            this._CurrentMouseDragStart = null;
            this._CurrentMouseDragOffset = null;
            this._UserDragPointOffset = null;
        }
        /// <summary>
        /// Returns a new GCurrentItem object for topmost interactive item on specified point
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        protected GCurrentItem FindItemAtPoint(Point point)
        {
            return this.FindItemAtPoint(point, false);
        }
        /// <summary>
        /// Returns a new GCurrentItem object for topmost interactive item on specified point
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        protected GCurrentItem FindItemAtPoint(Point point, bool withDisabled)
        {
            GCurrentItem gci = GCurrentItem.FindItemAtPoint(this.ItemsList, this._CurrentItem, point, withDisabled);
            this._CurrentMouseRelativeLocation = _GetRelativePoint(point, gci);
            return gci;
        }
        /// <summary>
        /// Return relative position of specified point to CurrentItem.ActiveArea.Location.
        /// I.e. for CurrentItem.ActiveArea.Location = {100,70} and point = {121,75} returns value {21,5} (= point - CurrentItem.ActiveArea.Location).
        /// When CurrentItem not exists return null.
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        protected Point? _GetRelativePointToCurrentItem(Point point)
        {
            return _GetRelativePoint(point, this._CurrentItem);
        }
        /// <summary>
        /// Return relative position of specified point to specified GCurrentItem (its CurrentItem.ActiveBounds.Location).
        /// I.e. for CurrentItem.ActiveArea.Location = {100,70} and point = {121,75} returns value {21,5} (= point - CurrentItem.ActiveArea.Location).
        /// When CurrentItem not exists return null.
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        protected static Point? _GetRelativePoint(Point point, GCurrentItem gCurrentItem)
        {
            return (gCurrentItem == null ? (Point?)null : (Point?)gCurrentItem.GetRelativePointToCurrentItem(point));
        }
        private GCurrentItem _CurrentItem;
        private GCurrentItem _LastClickItem;
        /// <summary>
        /// Coordinates of mouse, relative to thic control.
        /// </summary>
        private Point? _CurrentMouseAbsoluteLocation;
        /// <summary>
        /// Coordinates of mouse, relative to current interactive item bounds.
        /// </summary>
        private Point? _CurrentMouseRelativeLocation;
        /// <summary>
        /// Coordinates of mouse, in control coordinates, where mouse down occurs.
        /// </summary>
        private Point? _CurrentMouseDownLocation;
        private MouseButtons? _CurrentMouseDownButtons;
        private Rectangle? _CurrentMouseDragStart;
        private Point? _CurrentMouseDragOffset;
        /// <summary>
        /// Offset of UserDragPoint (from event DragBegin) relative to _CurrentMouseDownLocation (in control coordinates).
        /// This offset will be added to MouseLocation (in control coordinates) during drag, and sent to DragMove events.
        /// </summary>
        private Point? _UserDragPointOffset;
        private Size _DragStartSize;
        #endregion
        #endregion
        #region ReDraw control after interactive action (transform data from Mouse response to Draw)
        /// <summary>
        /// Init variables for requests to drawing from interactive methods
        /// </summary>
        private void _MouseDrawInit(MouseEventArgs e)
        {
            this.StopwatchStart();
            this._CurrentMouseAbsoluteLocation = (e == null ? (Point?)null : (Point?)e.Location);
            foreach (IInteractiveItem item in this.Items)
            {
                item.RepaintToLayers = GInteractiveDrawLayer.None;
                item.RepaintAllItems = false;
            }
            this._MouseCursorType = null;
            this._DrawState = InteractiveDrawState.InteractiveEvent;
        }
        /// <summary>
        /// Store request to drawing from one interactive event
        /// </summary>
        /// <param name="e"></param>
        private void _MouseDrawStore(GInteractiveChangeStateArgs e)
        {
            if (e.ToolTip != null)
                this._ToolTipSet(this._CurrentMouseAbsoluteLocation, e.ToolTip);
            if (e.RequiredCursorType.HasValue)
                this._MouseCursorType = e.RequiredCursorType;                // When multiple events occurs, valid CursorType is from last event. Typically sequence is: Item1.MouseLeave (Cursor = Default), Item2.MouseEnter (Cursor = VSplit).
        }
        /// <summary>
        /// Call Draw, if there are any request.
        /// </summary>
        private void _MouseDrawRun()
        {
            DrawRequest request = new DrawRequest();
            request.Fill(new Point(0, 0), this.ClientRectangle, this.ItemsList, this.PendingFullDraw, true);
            if (request.NeedAnyDraw)
            {
                try
                {
                    this._DrawState = InteractiveDrawState.InteractiveRepaint;
                    this.Draw(request);
                }
                finally
                {
                    this.StopwatchStop();
                    this._ActivateCursor(this._MouseCursorType);
                    this._DrawState = InteractiveDrawState.Standard;
                }
            }
            else
            {
                this.StopwatchDiscard();
                this._ActivateCursor(this._MouseCursorType);
            }
            this._MouseCursorType = null;
            this._DrawState = InteractiveDrawState.Standard;
        }
        /// <summary>
        /// Required cursor type.
        /// </summary>
        private SysCursorType? _MouseCursorType;
        #endregion
        #region Support for ToolTip
        private void _ToolTipSet(Point? point, string text)
        {
            this._ToolTip.Point = point;
            this._ToolTip.Text = text;
        }
        private void _ToolTipInit()
        {
            this._ToolTip = new ToolTipItem(this);
        }
        private void _TimerReset()
        {
        }
        private ToolTipItem _ToolTip;
        protected class ToolTipItem
        {
            public ToolTipItem(Control owner)
            {
                this._Owner = owner;
                this.TextFont = new Font(SystemFonts.DialogFont, FontStyle.Regular);
                this.BackColor = Color.FromArgb(224, SystemColors.Info);
                this.BorderColor = SystemColors.InfoText;
                this.TextColor = SystemColors.InfoText;
                this._InvalidateAll();
            }
            public Point? Point { get { return this._Point; } set { this._Point = value; this._InvalidatePath(); } }
            public string Text { get { return this._Text; } set { this._Text = value; this._InvalidateAll(); } }
            public Color BackColor { get; set; }
            public Font TextFont { get { return this._TextFont; } set { this._DisposeFont(); this._TextFont = value; this._InvalidateAll(); } }
            public Color TextColor { get; set; }
            public Color BorderColor { get; set; }
            public bool NeedDraw { get { return (this.Point.HasValue && !String.IsNullOrEmpty(this.Text) && this._TextFont != null); } }

            private Control _Owner;
            private Point? _Point;
            private string _Text;
            private Font _TextFont;
            
            private void _DisposeFont()
            {
                if (this._TextFont == null) return;
                this._TextFont.Dispose();
                this._TextFont = null;
            }
            private void _InvalidateAll()
            {
                this._TextSize = null;
                this._InvalidatePath();
            }
            private void _InvalidatePath()
            {
                this._TextBounds = null;
                this._OutlinePath = null;
                this._Position = ToolTipPosition.None;
            }
            /// <summary>
            /// Draw current tooltip
            /// </summary>
            /// <param name="graphics"></param>
            internal void Draw(Graphics graphics)
            {
                if (!this.NeedDraw) return;

                Rectangle outerBounds = Rectangle.Ceiling(this.OutlinePath.GetBounds());
                using (System.Drawing.Drawing2D.LinearGradientBrush lb = new System.Drawing.Drawing2D.LinearGradientBrush(outerBounds, this.BackColor.Morph(Color.White, 0.40f), this.BackColor.Morph(Color.Black, 0.30f), 90f))
                using (SolidBrush sb = new SolidBrush(Color.White))
                using (Pen pen = new Pen(Color.Black, 1f))
                using (GPainter.GraphicsUseSmooth(graphics))
                {
                    sb.Color = this.BackColor;
                    graphics.FillPath(lb, this.OutlinePath);

                    pen.Color = this.BorderColor;
                    graphics.DrawPath(pen, this.OutlinePath);

                    sb.Color = this.TextColor;
                    graphics.DrawString(this.Text, this.TextFont, sb, this.TextBounds);
                }
            }
            /// <summary>
            /// Size of text this.Text in font this.Font on current control graphics
            /// </summary>
            private Size TextSize
            {
                get
                {
                    if (!this.NeedDraw) return Size.Empty;
                    if (!this._TextSize.HasValue)
                    {
                        SizeF size = SizeF.Empty;
                        using (Graphics g = this._Owner.CreateGraphics())
                        {
                            size = g.MeasureString(this._Text, this._TextFont);
                        }
                        this._TextSize = Size.Ceiling(size);
                    }
                    return this._TextSize.Value;
                }
            } private Size? _TextSize;
            /// <summary>
            /// Bounds of text this.Text in font this.Font on current control graphics, at current this.Point
            /// </summary>
            private Rectangle TextBounds
            {
                get
                {
                    if (!this.NeedDraw) return Rectangle.Empty;
                    if (!this._TextBounds.HasValue)
                        this._CalculatePosition();
                    return this._TextBounds.Value;
                }
            } private Rectangle? _TextBounds;
            /// <summary>
            /// Bounds of all items in tooltip (icon, title, text) in current fonts on current control graphics, at current this.Point
            /// </summary>
            private Rectangle InnerBounds
            {
                get
                {
                    if (!this.NeedDraw) return Rectangle.Empty;
                    if (!this._InnerBounds.HasValue)
                        this._CalculatePosition();
                    return this._InnerBounds.Value;
                }
            } private Rectangle? _InnerBounds;
            /// <summary>
            /// Outline path of tooltip
            /// </summary>
            private System.Drawing.Drawing2D.GraphicsPath OutlinePath
            {
                get
                {
                    if (!this.NeedDraw) return null;
                    if (this._OutlinePath == null)
                        this._CalculatePosition();
                    return this._OutlinePath;
                }
            } private System.Drawing.Drawing2D.GraphicsPath _OutlinePath;
            /// <summary>
            /// Calculate position of tooltip on control.
            /// Fill values to _TextBounds and _OutlinePath.
            /// </summary>
            private void _CalculatePosition()
            {
                this._CalculatePositionRectangle();
            }
            /// <summary>
            /// Calculate position of tooltip on control as Rectangle.
            /// Fill values to _TextBounds and _OutlinePath.
            /// </summary>
            private void _CalculatePositionRectangle()
            {
                this._InvalidatePath();

                Point point = this.Point.Value;
                Size size = this.TextSize;
                Rectangle space = this._Owner.ClientRectangle;

                // AddTo TextSize (Width, Height):
                int aw = 3;
                int ah = 1;

                // Borders around text (Width, Height):
                int bw = 6;
                int bh = 6;

                // Borders around path (Width, Height):
                int pw = 4;
                int ph = 4;

                // Max text width and height:
                int mw = (size.Width <= 250 ? size.Width : space.Width * 2 / 3);
                int mh = (size.Height <= 75 ? size.Height : space.Height / 2);

                // Up/Down Y offset from point:
                int xdf = 14;
                int ydu = 5;
                int ydb = 20;

                // X point and Width:
                int x = point.X - xdf;                               // X: from cursor (X-10), to right
                int w = size.Width;
                if (w > mw) w = mw;
                if ((x + w + aw + bw) > space.Right)                 // Exceeds to Right:
                    x = space.Right - bw - aw - w;                   // X: from Right edge to left
                if (x < (space.Left + bw))                           // Exceeds to Left:
                {
                    x = space.Left + bw;                             // X: from Left edge to right
                    if ((x + w + aw + bw) > space.Right)             // Size exceeds disponible space, reduce width
                        w = space.Right - bw - aw - bw;
                }

                // Y point and Height:
                this._Position = ToolTipPosition.Under;
                int y = point.Y + ydb;                               // Y: from cursor (Y+20), down
                int h = size.Height;
                if (h > mh) h = mh;
                if ((y + h + ah + bh) > space.Bottom)                // Exceeds to Bottom: from cursor (Y-bh-ah-h), up
                {
                    this._Position = ToolTipPosition.Above;
                    y = point.Y - ydu - bh - ah - h;
                    if ((y + h + ah + bh) > space.Bottom)
                        y = space.Bottom - bh - ah - h;
                }
                if (y < (space.Top + bh))
                {
                    y = space.Top + bh;
                    if ((y + h + ah + bh) > space.Bottom)
                        h = space.Bottom - bh - ah - bh;
                }

                if (!space.Contains(point))
                    this._Position = ToolTipPosition.None;

                Rectangle tb = new Rectangle(x, y, w + aw, h + ah);
                this._TextBounds = tb;

                Rectangle ib = tb;
                this._InnerBounds = ib;

                Rectangle pb = tb;
                pb.Inflate(pw, ph);

                // Ellipse:
                Rectangle ellipse = GPainter.CreateEllipseRectangle(pb, 6);
                this._OutlinePath = new System.Drawing.Drawing2D.GraphicsPath();
                this._OutlinePath.AddEllipse(ellipse);

                // this._OutlinePath = Painter.CreatePathRoundRectangleWithArrow(pb, 2, 2, point);
            }
            private ToolTipPosition _Position;
            private enum ToolTipPosition
            {
                None,
                Above,
                Under
            }
            private void _CalculatePositionLineHandler(System.Drawing.Drawing2D.GraphicsPath gp, int line, Point point1, Point point2)
            {
                if ((this._Position == ToolTipPosition.Under && line == 0) || (this._Position == ToolTipPosition.Above && line == 2))
                {   // Create a bezier arrow from line (point1 => point2) to this.Point:
                    int xp = this.Point.Value.X;
                    int yp = this.Point.Value.Y;
                    int y = point1.Y;
                    double yh = this.Point.Value.Y - y;
                    if (yh < 0) yh = -yh;        // Height of arrow, positive number

                    int x0 = point1.X;
                    int x5 = point2.X;
                    double dx = x5 - x0;         // Width of whole line
                    double dw = 0.75d * (double)yh;     // Width of base of arrow = 75% of height
                    if (dx < 0d) dw = -dw;       // Same sign as dx
                    double mw = dx / 3d;         // Max width of base
                    if (mw < 0d) mw = -mw;

                    int x1 = xp + (int)(0.25d * dw);
                    if (dx > 0 && x1 < x0)
                        x1 = x0 + 4;
                    else if (dx < 0 && x1 > x0)
                        x1 = x0 - 4;

                    int x2 = x1 + (int)(0.25d * dw);
                    int x3 = x2 + (int)(0.50d * dw);
                    int x4 = x3 + (int)(0.25d * dw);

                    Point point3 = this.Point.Value;
                    gp.AddLine(x0, y, x1, y);
                    gp.AddBezier(x1, y, x2, y, x2, y, xp, yp);
                    gp.AddBezier(xp, yp, x3, y, x3, y, x4, y);
                    gp.AddLine(x4, y, x5, y);
                }
                else
                {
                    gp.AddLine(point1, point2);
                }
            }
        }
        #endregion
        #region Draw
        protected override bool CanDraw { get { return (this._DrawState == InteractiveDrawState.Standard || this._DrawState == InteractiveDrawState.InteractiveRepaint); } }
        /// <summary>
        /// Initialize Draw subsystem.
        /// </summary>
        private void _DrawInit()
        {
            this.LayerCount = 4;          // [0] = Standard;  [1] = Dynamic;  [2] = Interactive;  [3] = ToolTip and time info
        }
        /// <summary>
        /// Main paint process (standard and interactive)
        /// </summary>
        /// <param name="e"></param>
        protected override void OnPaintLayers(LayeredPaintEventArgs e)
        {
            DrawRequest request = e.UserData as DrawRequest;
            if (request == null)
            {
                request = new DrawRequest();
                request.Fill(new Point(0, 0), this.ClientRectangle, this.ItemsList, true, false);
            }

            if (request.NeedStdDraw)
            {
                if (request.DrawAllItems)
                    base.OnPaintLayers(e);
                this._PaintItems(e.GraphicsForLayer(0), request.ItemStdList, GInteractiveDrawLayer.Standard);
            }
            if (request.NeedIntDraw)
            {
                e.CopyContentOfLayer(e.ValidLayer, 1);
                this._PaintItems(e.GraphicsForLayer(1), request.ItemIntList, GInteractiveDrawLayer.Interactive);
            }
            if (request.NeedDynDraw)
            {
                e.CopyContentOfLayer(e.ValidLayer, 2);
                this._PaintItems(e.GraphicsForLayer(2), request.ItemDynList, GInteractiveDrawLayer.Dynamic);
            }

            if (this._ToolTip.NeedDraw)
            {
                e.CopyContentOfLayer(e.ValidLayer, 3);
                this._ToolTip.Draw(e.GraphicsForLayer(3));
            }

            this._PaintStopwatch(e);

            this._DrawState = InteractiveDrawState.Standard;
        }
        private void _PaintItems(Graphics graphics, IEnumerable<DrawRequestItem> items, GInteractiveDrawLayer drawLayer)
        {
            if (items != null)
            {
                GInteractiveDrawArgs e = new GInteractiveDrawArgs(graphics, drawLayer);
                foreach (DrawRequestItem item in items)
                {
                    graphics.ResetClip();
                    graphics.ResetTransform();
                    graphics.SetClip(item.OwnerBounds);
                    graphics.TranslateTransform(item.Offset.X, item.Offset.Y);

                    item.Item.Draw(e);
                    item.Item.RepaintAllItems = false;
                    item.Item.RepaintToLayers = (GInteractiveDrawLayer)((int)item.Item.RepaintToLayers & ((int)drawLayer ^ 0xFFFF));
                }
            }
        }
        /// <summary>
        /// Activate required cursor, when has value, and is changed from current cursor (see <see cref="_CurrentCursorType">_CurrentCursorType</see>).
        /// </summary>
        /// <param name="cursorType"></param>
        private void _ActivateCursor(SysCursorType? cursorType)
        {
            if (cursorType.HasValue && (!this._CurrentCursorType.HasValue || (this._CurrentCursorType.HasValue && this._CurrentCursorType != cursorType.Value)))
            {
                this.Cursor = SysCursors.GetCursor(cursorType.Value);
                this._CurrentCursorType = cursorType;
            }
        }
        /// <summary>
        /// Current cursor type. Null = default.
        /// </summary>
        private SysCursorType? _CurrentCursorType;
        private InteractiveDrawState _DrawState = InteractiveDrawState.Standard;
        private enum InteractiveDrawState { Standard = 0, InteractiveEvent, InteractiveRepaint }
        #region class DrawRequest + DrawRequestItem
        /// <summary>
        /// Class for analyze items to repaint/draw
        /// </summary>
        protected class DrawRequest
        {
            public DrawRequest()
            {
                this.ItemStdList = new List<DrawRequestItem>();
                this.ItemIntList = new List<DrawRequestItem>();
                this.ItemDynList = new List<DrawRequestItem>();
            }
            /// <summary>
            /// Items drawed to Standard layer
            /// </summary>
            public List<DrawRequestItem> ItemStdList { get; private set; }
            /// <summary>
            /// Items drawed to Interactive layer
            /// </summary>
            public List<DrawRequestItem> ItemIntList { get; private set; }
            /// <summary>
            /// Items drawed to Dynamic layer
            /// </summary>
            public List<DrawRequestItem> ItemDynList { get; private set; }
            public bool DrawAllItems { get; private set; }
            public bool InteractiveMode { get; private set; }
            public bool NeedStdDraw { get { return (this.ItemStdList.Count > 0); } }
            public bool NeedIntDraw { get { return (this.ItemIntList.Count > 0); } }
            public bool NeedDynDraw { get { return (this.ItemDynList.Count > 0); } }
            public bool NeedAnyDraw { get { return (this.NeedStdDraw || this.NeedIntDraw || this.NeedDynDraw); } }
            /// <summary>
            /// Fill this lists (this.ItemStdList, ItemIntList, ItemDynList) with items to draw by their state.
            /// Scan specified list of items, and where item is container, then recursively their items too.
            /// </summary>
            /// <param name="itemList"></param>
            /// <param name="drawAllItems"></param>
            /// <param name="interactive"></param>
            internal void Fill(Point offset, Rectangle bounds, List<IInteractiveItem> itemList, bool drawAllItems, bool interactive)
            {
                if (!drawAllItems)
                    drawAllItems = itemList.Any(i => i.IsVisible && i.RepaintAllItems);
                this.DrawAllItems = drawAllItems;
                this.InteractiveMode = interactive;

                this.FillFromList(offset, bounds, itemList);
            }
            /// <summary>
            /// Fill items for drawing from specified list
            /// </summary>
            /// <param name="bounds"></param>
            /// <param name="itemList"></param>
            /// <param name="interactive"></param>
            private void FillFromList(Point offset, Rectangle bounds, List<IInteractiveItem> itemList)
            {
                foreach (IInteractiveItem item in itemList)
                {
                    if (!item.IsVisible) continue;

                    if (this.CanDrawlItemToLayer(item, GInteractiveDrawLayer.Standard))
                        this.ItemStdList.Add(new DrawRequestItem(offset, bounds, item));
                    if (this.CanDrawlItemToLayer(item, GInteractiveDrawLayer.Interactive))
                        this.ItemIntList.Add(new DrawRequestItem(offset, bounds, item));
                    if (this.CanDrawlItemToLayer(item, GInteractiveDrawLayer.Dynamic))
                        this.ItemDynList.Add(new DrawRequestItem(offset, bounds, item));

                    if (item is IInteractiveContainer)
                        this.FillFromContainer(offset, bounds, item as IInteractiveContainer);
                }
            }
            /// <summary>
            /// Fill subitems from specified container
            /// </summary>
            /// <param name="bounds"></param>
            /// <param name="container"></param>
            /// <param name="interactive"></param>
            private void FillFromContainer(Point offset, Rectangle bounds, IInteractiveContainer container)
            {
                if (container == null || container.ItemList == null || container.ItemList.Count == 0) return;

                Rectangle cb = container.ActiveBounds;
                Point containerOffset = offset.Add(cb.Location);                         // Absolute position of container location
                Rectangle containerBounds = new Rectangle(containerOffset, cb.Size);     // Container absolute bounds
                Rectangle visibleBounds = Rectangle.Intersect(bounds, containerBounds);  // Absolute bounds of visible part of container

                // same proces for ItemBounds:
                Rectangle ib = container.ItemBounds;
                Point itemOffset = containerOffset.Add(ib.Location);
                Rectangle itemBounds = new Rectangle(itemOffset, ib.Size);
                visibleBounds = Rectangle.Intersect(visibleBounds, itemBounds);

                this.FillFromList(itemOffset, visibleBounds, container.ItemList);        // Pure recursive loop
            }
            /// <summary>
            /// Return true, when item has been to draw to specified layer.
            /// </summary>
            /// <param name="item"></param>
            /// <param name="layer"></param>
            /// <returns></returns>
            private bool CanDrawlItemToLayer(IInteractiveItem item, GInteractiveDrawLayer layer)
            {
                bool isStdToLayer = ((item.StandardDrawToLayer & layer) != 0);
                bool isDynToLayer = ((item.RepaintToLayers & layer) != 0);
                if (this.InteractiveMode)
                {
                    if (this.DrawAllItems)
                        return (isStdToLayer || isDynToLayer);
                    return isDynToLayer;
                }
                return isStdToLayer;
            }
        }
        /// <summary>
        /// Data for drawing one item
        /// </summary>
        protected class DrawRequestItem
        {
            public DrawRequestItem(Point offset, Rectangle ownerBounds, IInteractiveItem item)
            {
                this.Offset = offset;
                this.OwnerBounds = ownerBounds;
                this.Item = item;
            }
            public Point Offset { get; set; }
            public Rectangle OwnerBounds { get; set; }
            public IInteractiveItem Item { get; set; }
        }
        #endregion
        #endregion
        #region StopWatch
        /// <summary>
        /// Initialize Stopwatch
        /// </summary>
        private void _StopWatchInit()
        {
            this._StopwatchQueue = new Queue<decimal>();
            this._StopwatchFrequency = (decimal)System.Diagnostics.Stopwatch.Frequency;
            this.Stopwatch = new System.Diagnostics.Stopwatch();
        }
        /// <summary>
        /// Start new time-measure
        /// </summary>
        protected void StopwatchStart()
        {
            this.Stopwatch.Restart();
        }
        /// <summary>
        /// Stop current time-measure and save results
        /// </summary>
        protected void StopwatchStop()
        {
            if (this.Stopwatch.IsRunning)
            {
                this.Stopwatch.Stop();
                decimal time = (decimal)this.Stopwatch.ElapsedTicks / this._StopwatchFrequency;
                while (this._StopwatchQueue.Count >= 50)
                    this._StopwatchQueue.Dequeue();
                this._StopwatchQueue.Enqueue(time);
                this.StopwatchLastTime = time;
            }
        }
        /// <summary>
        /// Stop current time-measure and discard results
        /// </summary>
        protected void StopwatchDiscard()
        {
            if (this.Stopwatch.IsRunning)
            {
                this.Stopwatch.Stop();
            }
        }
        /// <summary>
        /// Paint stopwatch info to control
        /// </summary>
        /// <param name="e"></param>
        private void _PaintStopwatch(LayeredPaintEventArgs e)
        {
            decimal avt = this.StopwatchAverageTime;
            if (avt <= 0m) return;

            e.CopyContentOfLayer(e.ValidLayer, 3);

            decimal fps = Math.Round(1m / avt, 1);
            string info = "     " + fps.ToString("### ##0.0").Trim() + " fps";
            Font font = SystemFonts.StatusFont;
            Size size = new System.Drawing.Size(90, 20);
            Rectangle bounds = size.AlignTo(this.ClientRectangle, ContentAlignment.BottomRight).ResizeBy(0, 0, -1, -1);
            Graphics graphics = e.GraphicsForLayer(e.ValidLayer);
            Color backColor = Color.FromArgb(180, Color.LightSkyBlue);
            Color foreColor = Color.FromArgb(220, Color.Black);
            GraphicsPath gp = GPainter.CreatePathRoundRectangle(bounds, 2, 2);

            using (GPainter.GraphicsUseSmooth(graphics))
            {
                using (Brush b = GPainter.CreateBrushForBackground(bounds, Orientation.Horizontal, GInteractiveState.None, backColor))
                using (Pen p = new Pen(foreColor))
                {
                    graphics.FillPath(b, gp);
                    graphics.DrawPath(p, gp);
                }

                GPainter.DrawString(graphics, bounds, info, foreColor, font, ContentAlignment.MiddleCenter);
            }
        }
        /// <summary>
        /// Time from last measure, in seconds
        /// </summary>
        protected decimal StopwatchLastTime { get; private set; }
        /// <summary>
        /// Average time from last (max 20) measure, in seconds
        /// </summary>
        protected decimal StopwatchAverageTime { get { return (this._StopwatchQueue.Count == 0 ? 0m : this._StopwatchQueue.Average()); } }
        private Queue<decimal> _StopwatchQueue;
        private decimal _StopwatchFrequency;
        protected System.Diagnostics.Stopwatch Stopwatch { get; private set; }
        #endregion
        #region Public property and events
        /// <summary>
        /// Interactive items.
        /// Any collection can be stored.
        /// Set of value trigger this.Draw().
        /// </summary>
        [Browsable(false)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public IEnumerable<IInteractiveItem> Items
        {
            get { return this.ItemsList; }
            set { this._ItemsList = new List<IInteractiveItem>(value); this.Draw(); }
        }
        /// <summary>
        /// Add one interactive item.
        /// Does not trigger Draw().
        /// </summary>
        /// <param name="item"></param>
        public void AddItem(IInteractiveItem item)
        {
            this.ItemsList.Add(item);
        }
        /// <summary>
        /// Add more interactive items.
        /// Does not trigger Draw().
        /// </summary>
        /// <param name="item"></param>
        public void AddItems(params IInteractiveItem[] items)
        {
            this.ItemsList.AddRange(items);
        }
        /// <summary>
        /// Add more interactive items.
        /// Does not trigger Draw().
        /// </summary>
        /// <param name="item"></param>
        public void AddItems(IEnumerable<IInteractiveItem> items)
        {
            this.ItemsList.AddRange(items);
        }
        /// <summary>
        /// Event after change of interactive state
        /// </summary>
        public event GInteractiveChangeStateHandler InteractiveStateChanged;
        private void _OnInteractiveStateChanged(GInteractiveChangeStateArgs e)
        {
            this.OnInteractiveStateChanged(e);
            if (this.InteractiveStateChanged != null)
                this.InteractiveStateChanged(this, e);
        }
        protected virtual void OnInteractiveStateChanged(GInteractiveChangeStateArgs e) { }
        #endregion
    }
    #region class GCurrentItem : Working class for one current interactive item
    /// <summary>
    /// Working class for one current interactive item
    /// </summary>
    public class GCurrentItem
    {
        #region Constructor, basic properties
        private GCurrentItem(Point mousePoint)
        {
            this.ExistsSubItem = false;
            this.FoundItemList = new List<FindItemPoint>();
            this.MousePoint = mousePoint;
            this.EventTime = DateTime.Now;
        }
        private GCurrentItem(bool existsItem, IInteractiveItem item, bool existsSubItem, IInteractiveSubItem subItem, Point mousePoint)
        {
            this.ExistsSubItem = existsSubItem;
            this.CurrentSubItem = subItem;
            this.MousePoint = mousePoint;
            this.EventTime = DateTime.Now;
        }
        /// <summary>
        /// List of chain found items from root up to CurrentItem.
        /// At first index is item at root. CurrentItem is last item of this list.
        /// </summary>
        public List<FindItemPoint> FoundItemList { get; protected set; }
        /// <summary>
        /// true when any item is found
        /// </summary>
        public bool ExistsItem { get { return (this.FoundItemList.Count > 0); } }
        /// <summary>
        /// Found item
        /// </summary>
        public IInteractiveItem CurrentItem { get { return (this.ExistsItem ? this.FoundItemList[this.FoundItemList.Count - 1].Item : null);} }
        /// <summary>
        /// Absolute coordinates of item in coordinate system of Control
        /// </summary>
        public Point? CurrentItemLocation { get { return (this.ExistsItem ? (Point?)this.FoundItemList[this.FoundItemList.Count - 1].ItemAbsoluteLocation : (Point?)null); } }
        /// <summary>
        /// Relative coordinates of mouse in coordinate system of CurrentItem
        /// </summary>
        public Point? CurrentItemMouseRelativePoint { get { return (this.ExistsItem ? (Point?)this.FoundItemList[this.FoundItemList.Count - 1].RelativeMousePoint : (Point?)null); } }
        /// <summary>
        /// true when found SubItem on current Item
        /// </summary>
        public bool ExistsSubItem { get; protected set; }
        /// <summary>
        /// Current SubItem (or null)
        /// </summary>
        public IInteractiveSubItem CurrentSubItem { get; protected set; }
        public Point MousePoint { get; protected set; }
        public DateTime EventTime { get; protected set; }
        #endregion
        #region Property from this.CurrentItem.Style, IsEnabled, IsVisible
        public bool CanMouse { get { return _HasStyle(GInteractiveStyles.Mouse); } }
        public bool CanClick { get { return _HasStyle(GInteractiveStyles.Click); } }
        public bool CanDoubleClick { get { return _HasStyle(GInteractiveStyles.DoubleClick); } }
        public bool CanDrag { get { return _HasStyle(GInteractiveStyles.Drag); } }
        public bool CanOver { get { return _HasStyle(GInteractiveStyles.CallMouseOver); } }
        public bool IsEnabled { get { return (this.CurrentItem != null && this.CurrentItem.IsEnabled); } }
        public bool IsVisible { get { return (this.CurrentItem != null && this.CurrentItem.IsVisible); } }
        private bool _HasStyle(GInteractiveStyles style)
        {
            return (this.CurrentItem != null && ((this.CurrentItem.Style & style) != 0));
        }
        #endregion
        #region FindItem, MapExchange
        /// <summary>
        /// Returns a new GCurrentItem object for topmost interactive item on specified point, in list of items.
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public static GCurrentItem FindItemAtPoint(List<IInteractiveItem> list, Point point)
        {
            return _FindItemAtPoint(list, null, point, false);
        }
        /// <summary>
        /// Returns a new GCurrentItem object for topmost interactive item on specified point, in list of items.
        /// Prefered current item above other items.
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public static GCurrentItem FindItemAtPoint(List<IInteractiveItem> list, GCurrentItem currentItem, Point point)
        {
            return _FindItemAtPoint(list, currentItem, point, false);
        }
        /// <summary>
        /// Returns a new GCurrentItem object for topmost interactive item on specified point.
        /// Accept disabled items (by parameter withDisabled).
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public static GCurrentItem FindItemAtPoint(List<IInteractiveItem> list, Point point, bool withDisabled)
        {
            return _FindItemAtPoint(list, null, point, withDisabled);
        }
        /// <summary>
        /// Returns a new GCurrentItem object for topmost interactive item on specified point
        /// Prefered current item above other items.
        /// Accept disabled items (by parameter withDisabled).
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public static GCurrentItem FindItemAtPoint(List<IInteractiveItem> list, GCurrentItem currentItem, Point point, bool withDisabled)
        {
            return _FindItemAtPoint(list, currentItem, point, withDisabled);
        }
        /// <summary>
        /// Returns a new GCurrentItem object for topmost interactive item on specified point
        /// Prefered current item above other items.
        /// Accept disabled items (by parameter withDisabled).
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        private static GCurrentItem _FindItemAtPoint(List<IInteractiveItem> list, GCurrentItem currentItem, Point point, bool withDisabled)
        {
            GCurrentItem result = new GCurrentItem(point);

            List<FindItemPoint> foundList = new List<FindItemPoint>();
            
            // Search for current item: when have an existing current item (currentItem) then search for its FoundItemList preferred:
            if (currentItem != null && currentItem.ExistsItem)
                foundList = _TryFindChildItemAtPoint(list, point, withDisabled, currentItem.FoundItemList);
            else
                foundList = _TryFindChildItemAtPoint(list, point, withDisabled, null);

            result.FoundItemList.AddRange(foundList);

            bool foundSameItem = ((currentItem != null && currentItem.ExistsItem) && foundList != null
                && foundList.Count > 0 && currentItem.FoundItemList.Count == foundList.Count
                && Object.ReferenceEquals(foundList[foundList.Count - 1], currentItem.FoundItemList[currentItem.FoundItemList.Count - 1]));

            // Search for subitem:
            if (result.ExistsItem && result.CurrentItem.SubItems != null)
            {
                Point subPoint = result.CurrentItemMouseRelativePoint.Value;
                
                // true to prefere current active subitem (when contain specified point) before any other subitem
                bool preferSubItem = (foundSameItem && currentItem.ExistsSubItem && currentItem.CurrentSubItem.HoldTheMouseAttention);
                if (foundSameItem && currentItem.ExistsSubItem && currentItem.CurrentSubItem.HoldTheMouseAttention && currentItem.CurrentSubItem.RelativeActiveBounds.Contains(subPoint))
                {
                    result.CurrentSubItem = currentItem.CurrentSubItem;
                    result.ExistsSubItem = true;
                }
                else
                {
                    foreach (IInteractiveSubItem s in result.CurrentItem.SubItems.Reverse())
                    {
                        if (s.RelativeActiveBounds.Contains(subPoint))
                        {
                            result.CurrentSubItem = s;
                            result.ExistsSubItem = true;
                            break;
                        }
                    }
                }
            }

            return result;
        }
        /// <summary>
        /// Search for item at specified point in list of items:
        /// item must be visible, must be active at point, and can be enabled, by parametrer withDisabled.
        /// If satisfactory item is IInteractiveContainer, then search continue in its ItemList.
        /// Lists are searched in order from last item to first.
        /// </summary>
        /// <param name="point"></param>
        /// <param name="list"></param>
        /// <param name="withDisabled"></param>
        /// <param name="resultList"></param>
        /// <returns></returns>
        private static List<FindItemPoint> _TryFindChildItemAtPoint(List<IInteractiveItem> list, Point point, bool withDisabled, List<FindItemPoint> preferList)
        {
            List<FindItemPoint> resultList = new List<FindItemPoint>();
            Queue<FindItemPoint> preferredQueue = (preferList == null ? new Queue<FindItemPoint>() : new Queue<FindItemPoint>(preferList));       // Chain of preferred items
            Point itemLocation = new Point(0, 0);
            bool run = true;
            while (run)
            {
                run = false;
                int foundAt = -1;
                if (preferredQueue.Count > 0)
                {   // Preferred item is stil active?
                    FindItemPoint fip = preferredQueue.Dequeue();
                    foundAt = _FindIndexOfItem(list, point, withDisabled, fip.Item);
                    if (foundAt < 0)
                        preferredQueue.Clear();
                }
                if (foundAt < 0)
                {   // Search with no preferred item, only by active point:
                    foundAt = _FindIndexOfItem(list, point, withDisabled, null);
                }

                if (foundAt >= 0)
                {
                    IInteractiveItem item = list[foundAt];

                    // Item already is in parentList => there is an infinite loop:
                    if (resultList.Any(iii => Object.ReferenceEquals(iii, item)))
                        throw new StackOverflowException("There is an cycling loop in nesting of IInteractiveItem list");
                        
                    // Found:
                    Point relPoint = item.ActiveBounds.Location;     // Location of item on its container
                    itemLocation = itemLocation.Add(relPoint);       // Coordinate of item.Location on Control
                    Point relativeMousePoint = point.Sub(relPoint);  // Relative mouse point on item (value in "point" is allways relative to current container)
                    resultList.Add(new FindItemPoint(item, itemLocation, relativeMousePoint));

                    // Founded item is a container => scan its SubItems in next loop:
                    if (item is IInteractiveContainer)
                    {
                        IInteractiveContainer container = item as IInteractiveContainer;
                        list = container.ItemList;
                        point = point.Sub(container.ActiveBounds.Location);
                        run = true;
                    }
                }
            }
            return resultList;
        }
        /// <summary>
        /// Search for item with max index in (list), 
        /// where item (is equal to preferredItem or preferredItem == null), 
        /// and item is visible and (enabled or withDisabled), and item IsActiveAtPoint(point).
        /// Return index of this item, or -1 when not found.
        /// </summary>
        /// <param name="list"></param>
        /// <param name="point"></param>
        /// <param name="withDisabled"></param>
        /// <param name="preferredItem"></param>
        /// <returns></returns>
        private static int _FindIndexOfItem(List<IInteractiveItem> list, Point point, bool withDisabled, IInteractiveItem preferredItem)
        {
            for (int idx = list.Count - 1; idx >= 0; idx--)
            {   // Search item in order from last to first visible item:
                IInteractiveItem item = list[idx];
                if ((preferredItem == null || Object.ReferenceEquals(preferredItem, item)) 
                    && item.IsVisible 
                    && (withDisabled || item.IsEnabled) 
                    && item.IsActiveAtPoint(point))
                {   // This item contain specified point:
                    return idx;
                }
            }
            return -1;
        }
        /// <summary>
        /// Return a relative point to current item
        /// </summary>
        /// <param name="point"></param>
        /// <returns></returns>
        public Point? GetRelativePointToCurrentItem(Point point)
        {
            return (this.ExistsItem ? (Point?)point.Sub(this.CurrentItemLocation.Value) : (Point?)null);
        }
        /// <summary>
        /// Create maps for calling events MouseLeave, MouseLeaveSubItem, MouseEnter, MouseEnterSubItem.
        /// </summary>
        /// <param name="prev"></param>
        /// <param name="next"></param>
        /// <param name="leaveList"></param>
        /// <param name="leaveSubItem"></param>
        /// <param name="enterList"></param>
        /// <param name="enterSubItem"></param>
        public static void MapExchange(GCurrentItem prev, GCurrentItem next, out List<IInteractiveItem> leaveList, out IInteractiveItem leaveSubItem, out List<IInteractiveItem> enterList, out IInteractiveItem enterSubItem)
        {
            leaveList = new List<IInteractiveItem>();
            leaveSubItem = null;
            enterList = new List<IInteractiveItem>();
            enterSubItem = null;

            if (prev == null && next == null) return;

            int prevCnt = (prev == null ? 0 : prev.FoundItemList.Count);
            int nextCnt = (next == null ? 0 : next.FoundItemList.Count);
            int bothCnt = (prevCnt < nextCnt ? prevCnt : nextCnt);             // Maximum count of items from both list (smallest from prev/next list)

            // Search for topmost index of an item, where are (on same level in booth GCurrentItem) contained same item in prev and next list:
            int sameIdx = -1;
            for (int i = 0; i < bothCnt; i++)
            {
                if (Object.ReferenceEquals(prev.FoundItemList[i].Item, next.FoundItemList[i].Item))
                    sameIdx = i;
                else
                    break;
            }

            int prevIdx = prevCnt - 1;
            int nextIdx = nextCnt - 1;

            // Is sameIndex at last item in booth list?
            bool isSameSubItem = false;
            if (sameIdx >= 0 && prevIdx == sameIdx && nextIdx == sameIdx)
            {
                if (!prev.ExistsSubItem && !next.ExistsSubItem)
                    return;                                          // Nothing action, when prev and next are same item and both have no subitem
                if (prev.ExistsSubItem && next.ExistsSubItem)
                    isSameSubItem = (Object.ReferenceEquals(prev.CurrentSubItem, next.CurrentSubItem));
                if (isSameSubItem)
                    return;                                          // Nothing action, when prev and next are same item and both have same subitem
            }

            // 1. MouseLeave subitem in prev object (store item to MouseLeaveSubItem):
            if (prev != null && prev.ExistsSubItem)
                leaveSubItem = prev.FoundItemList[prevIdx].Item;

            // 2. MouseLeave items from last index in prev, to item at [sameIdx+1] (fill "MouseLeave" list):
            for (int i = prevIdx; i >= (sameIdx + 1); i--)
            {
                IInteractiveItem item = prev.FoundItemList[i].Item;
                leaveList.Add(item);
            }

            // 3. MouseEnter items in next, from [sameIdx+1] to last index (fill "MouseEnter" list):
            for (int i = (sameIdx + 1); i <= nextIdx; i++)
            {
                IInteractiveItem item = next.FoundItemList[i].Item;
                enterList.Add(item);
            }

            // 4. MouseEnter subitem in next object (store item to MouseEnterSubItem):
            if (next != null && next.ExistsSubItem)
                enterSubItem = next.FoundItemList[nextIdx].Item;
        }
        #region class FindItemPoint : Found item and relative mouse location
        /// <summary>
        /// FindItemPoint : Found item and relative mouse location
        /// </summary>
        public class FindItemPoint
        {
            public FindItemPoint(IInteractiveItem item, Point itemLocation, Point relativeMousePoint)
            {
                this.Item = item;
                this.ItemAbsoluteLocation = itemLocation;
                this.RelativeMousePoint = relativeMousePoint;
            }
            /// <summary>
            /// Found item
            /// </summary>
            public IInteractiveItem Item { get; private set; }
            /// <summary>
            /// Item absolute location on Control
            /// </summary>
            public Point ItemAbsoluteLocation { get; private set; }
            /// <summary>
            /// Mouse point in coordinates of this item (where Item.ActiveBounds.Location = 0;0)
            /// </summary>
            public Point RelativeMousePoint { get; private set; }
        }
        #endregion
        #endregion
        #region Static services
        /// <summary>
        /// Return true, when specified GCurrentItem is not null and its Item exists
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public static bool HasItem(GCurrentItem item)
        {
            return (item != null && item.ExistsItem);
        }
        /// <summary>
        /// Return true, when is change between prev a next.
        /// I.e. when exact one of (prev, next) has item (HasItem()), or prev and next contain different item.
        /// </summary>
        /// <param name="prev"></param>
        /// <param name="next"></param>
        /// <returns></returns>
        public static bool IsExchange(GCurrentItem prev, GCurrentItem next)
        {
            bool phi = GCurrentItem.HasItem(prev);
            bool nhi = GCurrentItem.HasItem(next);
            if (phi && nhi) return !Object.ReferenceEquals(prev.CurrentItem, next.CurrentItem);       // from item A to item B
            if (!phi && !nhi) return false;            // from None to None
            return true;                               // from object to none, or from none to object
        }
        /// <summary>
        /// return true, when items lastItem and currItem are equal, can doubleclick, 
        /// and time between events is in limit to doubleclick.
        /// </summary>
        /// <param name="lastItem"></param>
        /// <param name="currItem"></param>
        /// <returns></returns>
        public static bool IsDoubleClick(GCurrentItem lastItem, GCurrentItem currItem)
        {
            if (lastItem == null || currItem == null) return false;
            if (!lastItem.ExistsItem || !currItem.ExistsItem) return false;
            if (!currItem.CanDoubleClick) return false;
            if (!Object.ReferenceEquals(lastItem.CurrentItem, currItem.CurrentItem)) return false;
            TimeSpan time = currItem.EventTime.Subtract(lastItem.EventTime);
            return IsDoubleClick(lastItem.MousePoint, currItem.MousePoint, time);
        }
        /// <summary>
        /// Return true, when second mouse click (at point2), which is (time) after first click (at point1) is DoubleClick.
        /// </summary>
        /// <param name="point1"></param>
        /// <param name="point2"></param>
        /// <param name="time"></param>
        /// <returns></returns>
        public static bool IsDoubleClick(Point point1, Point point2, TimeSpan time)
        {
            if (time > DoubleClickTime) return false;
            Rectangle area = point1.CreateRectangleFromCenter(DoubleClickSize);
            return area.Contains(point2);
        }
        /// <summary>
        /// DoubleClickTime (SystemInformation.DoubleClickTime as TimeSpan)
        /// </summary>
        public static TimeSpan DoubleClickTime
        {
            get
            {
                if (!_DoubleClickTime.HasValue)
                    _DoubleClickTime = TimeSpan.FromMilliseconds(SystemInformation.DoubleClickTime);
                return _DoubleClickTime.Value;
            }
        }
        /// <summary>
        /// DoubleClickSize
        /// </summary>
        public static Size DoubleClickSize
        {
            get
            {
                if (!_DoubleClickOffset.HasValue)
                    _DoubleClickOffset = SystemInformation.DoubleClickSize;
                return _DoubleClickOffset.Value;
            }
        }
        private static TimeSpan? _DoubleClickTime;
        private static Size? _DoubleClickOffset;
        #endregion
    }
    #endregion
    #region Delegates and EventArgs
    /// <summary>
    /// Delegate for handlers of interactive event in GInteractiveControl
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public delegate void GInteractiveChangeStateHandler(object sender, GInteractiveChangeStateArgs e);
    /// <summary>
    /// Delegate for handlers of drawing event in GInteractiveControl
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public delegate void GInteractiveDrawHandler(object sender, GInteractiveDrawArgs e);
    /// <summary>
    /// Data for handlers of interactive event in GInteractiveControl
    /// </summary>
    public class GInteractiveChangeStateArgs : EventArgs
    {
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="existsItem"></param>
        /// <param name="currentItem"></param>
        /// <param name="changeState"></param>
        /// <param name="mouseRelativePoint"></param>
        /// <param name="dragToBounds"></param>
        public GInteractiveChangeStateArgs(bool existsItem, IInteractiveItem currentItem, IInteractiveSubItem currentSubItem, GInteractiveChangeState changeState, GInteractiveState currentState, Point? mouseRelativePoint, Rectangle? dragToBounds)
        {
            this.ExistsItem = existsItem;
            this.CurrentItem = currentItem;
            this.CurrentSubItem = currentSubItem;
            this.ChangeState = changeState;
            this.CurrentState = currentState;
            this.MouseRelativePoint = mouseRelativePoint;
            this.DragToBounds = dragToBounds;
        }
        /// <summary>
        /// true, when CurrentItem is found.
        /// Whereby CurrentItem is interface (i.e. can be a struct), then test for CurrentItem == null is not possible.
        /// </summary>
        public bool ExistsItem { get; protected set; }
        /// <summary>
        /// Active item
        /// </summary>
        public IInteractiveItem CurrentItem { get; protected set; }
        /// <summary>
        /// Active sub-item
        /// </summary>
        public IInteractiveSubItem CurrentSubItem { get; protected set; }
        /// <summary>
        /// Type of event (change status)
        /// </summary>
        public GInteractiveChangeState ChangeState { get; protected set; }
        /// <summary>
        /// Current (new) state of item (after this event, not before it).
        /// </summary>
        public GInteractiveState CurrentState { get; protected set; }
        /// <summary>
        /// Coordinate of mouse relative to CurrentItem.Area.Location.
        /// Can be a null, in case when ExistsItem is false.
        /// </summary>
        public Point? MouseRelativePoint { get; protected set; }
        /// <summary>
        /// Target area during Drag operation (in DragMove event)
        /// </summary>
        public Rectangle? DragToBounds { get; protected set; }
        /// <summary>
        /// User defined point during Drag operation.
        /// User (an IInteractiveItem) can set any point in event LeftDragBegin/RightDragBegin;
        /// then GInteractiveControl will be calculated appropriate moved point during Drag, 
        /// and this "dragged" point coordinates are stored to this property (UserDragPoint) before call event LeftDragMove/RightDragMove.
        /// When user in event DragBegin does not set any location (null value), then in event DragMove will be in this property null value.
        /// For other events this property does not have any meaning.
        /// </summary>
        public Point? UserDragPoint { get; set; }
        /// <summary>
        /// Required Cursor type. Null = default. Control detect change from curent state, CursorType can be set to required value everytime.
        /// </summary>
        public SysCursorType? RequiredCursorType { get; set; }
        /// <summary>
        /// Show this text as tooltip.
        /// When value is null, then tooltip does not changed.
        /// When value is set to empty string, then tooltip goes out.
        /// Text can contain line-break.
        /// </summary>
        public string ToolTip { get; set; }
    }
    /// <summary>
    /// Data for handlers of drawing event in GInteractiveControl
    /// </summary>
    public class GInteractiveDrawArgs : EventArgs
    {
        public GInteractiveDrawArgs(Graphics graphics, GInteractiveDrawLayer drawLayer)
        {
            this.Graphics = graphics;
            this.DrawLayer = drawLayer;
        }
        public Graphics Graphics { get; private set; }
        public GInteractiveDrawLayer DrawLayer { get; private set; }
    }
    #endregion
    #region Enums
    [Flags]
    public enum GInteractiveStyles
    {
        None = 0,
        /// <summary>
        /// Area is active for mouse move
        /// </summary>
        Mouse = 0x0001,
        /// <summary>
        /// Area can be clicked (left, right)
        /// </summary>
        Click = 0x0002,
        /// <summary>
        /// Area can be double-clicked (left, right)
        /// </summary>
        DoubleClick = 0x0004,
        /// <summary>
        /// Area can be dragged
        /// </summary>
        Drag = 0x0008,
        /// <summary>
        /// Call event MouseOver for MouseMove for each pixel (none = call only MouseEnter and MouseLeave)
        /// </summary>
        CallMouseOver = 0x0010,
        /// <summary>
        /// Enables move of item
        /// </summary>
        DragMove = 0x0100,
        /// <summary>
        /// Enables resize of item in X axis
        /// </summary>
        DragResizeX = 0x0200,
        /// <summary>
        /// Enables resize of item in Y axis
        /// </summary>
        DragResizeY = 0x0400,
        /// <summary>
        /// Enables resize of item in X and Y axis
        /// </summary>
        DragResize = DragResizeX | DragResizeY | Drag,
        /// <summary>
        /// Enables move and resize of item
        /// </summary>
        DragMoveResize = DragMove | DragResizeX | DragResizeY | Drag,
        /// <summary>
        /// Standard = Mouse | Click | DoubleClick | Drag. Not contain CallMouseOver.
        /// </summary>
        StandardInteractivity = Mouse | Click | DoubleClick | Drag,
        /// <summary>
        /// All = Mouse | Click | DoubleClick | Drag | CallMouseOver.
        /// </summary>
        AllInteractivity = StandardInteractivity | CallMouseOver,
    }
    public enum GInteractiveState
    {
        None = 0,
        Mouse,
        LeftDown,
        RightDown,
        LeftDrag,
        RightDrag
    }
    public enum GInteractiveChangeState
    {
        None = 0,
        MouseEnter,
        /// <summary>
        /// Is called only for item with style containing CallMouseOver !
        /// </summary>
        MouseOver,
        MouseOverDisabled,
        MouseLeave,
        MouseEnterSubItem,
        MouseLeaveSubItem,
        LeftDown,
        LeftDragBegin,
        LeftDragMove,
        LeftDragEnd,
        LeftUp,
        LeftClick,
        LeftDoubleClick,
        RightDown,
        RightDragBegin,
        RightDragMove,
        RightDragEnd,
        RightUp,
        RightClick,
        RightDoubleClick,
        WheelUp,
        WheelDown
    }
    /// <summary>
    /// Layers to draw
    /// </summary>
    [Flags]
    public enum GInteractiveDrawLayer : int
    {
        None = 0,
        /// <summary>
        /// Standard layer (static image)
        /// </summary>
        Standard = 1,
        /// <summary>
        /// Interactive layer (image from static layer, during drag operation)
        /// </summary>
        Interactive = 2,
        /// <summary>
        /// Dynamic layer (lines above standard and interactive layers)
        /// </summary>
        Dynamic = 4
    }
    #endregion
    #region interface IInteractiveContainer, IInteractiveItem, IInteractiveSubItem
    /// <summary>
    /// Define properties and methods for an object, which is container for collection of IInteractiveItem
    /// </summary>
    public interface IInteractiveContainer : IInteractiveItem
    {
        /// <summary>
        /// List of items belonging to this container
        /// </summary>
        List<IInteractiveItem> ItemList { get; }
        /// <summary>
        /// Bounds for inner items.
        /// In this cordinates are active and drawed item from this.ItemList.
        /// This coordinates are relative to this.ActiveBounds.
        /// Thus: ActiveBounds are coordinates in this parent; 
        /// in this.ActiveBounds are this.ItemBounds; an in ItemBounds are this.ItemList.
        /// When have InteractiveControl with ClientRectangle = {0,0,1000,600}, 
        /// in this control is an InteractiveContainer on coordinates ActiveBounds = {100, 50, 300, 200},
        /// that container have ItemBounds = { 10, 10, 280, 180 } = in center of container (with border 10 px),
        /// and in that container is in ItemList an item with ActiveBounds = { 60, 40, 20, 20 }, 
        /// then this item are on coordinates: 
        /// X = (InteractiveControl.ClientRectangle.X + InteractiveContainer.ActiveBounds.X + InteractiveContainer.ItemBounds.X + item.ActiveBounds.X = 0 + 100 + 10 + 60 = 170px
        /// </summary>
        Rectangle ItemBounds { get; }
    }
    /// <summary>
    /// Define properties and methods for an object, which is interactive on InteractiveControl.
    /// </summary>
    public interface IInteractiveItem
    {
        /// <summary>
        /// Parent of this item (its host). Can be null.
        /// Parent is typically an IInteractiveContainer.
        /// Item hosted directly on InteractiveControl has Parent = null.
        /// </summary>
        IInteractiveItem Parent { get; }
        /// <summary>
        /// Coordinates, where item is mouse-interactive, 
        /// in coordinate system of parent control (GInteractiveControl or IInteractiveContainer).
        /// </summary>
        Rectangle ActiveBounds { get; }
        /// <summary>
        /// Return true, when item is active at specified point.
        /// Point is in absolute coordinates (on Control).
        /// Item can be interactive on standard rectangle (Bounds), then test activity by method Rectangle.Contains(point).
        /// Or item can be active on multiple rectangles, or GraphicsPath...
        /// </summary>
        /// <param name="point">Point in Control coordinates, for which are test performed (typically point of mouse)</param>
        /// <returns></returns>
        Boolean IsActiveAtPoint(Point point);
        /// <summary>
        /// Interactive style
        /// </summary>
        GInteractiveStyles Style { get; }
        /// <summary>
        /// Is item currently visible?
        /// When true, then item can be interactive and can be Drawed.
        /// </summary>
        Boolean IsVisible { get; }
        /// <summary>
        /// Is item currently enabled?
        /// When true, then item can be interactive (and can be Drawed).
        /// When false, hen item can NOT be interactive, but can be Drawed.
        /// </summary>
        Boolean IsEnabled { get; }
        /// <summary>
        /// An array of sub-items in this item
        /// </summary>
        IEnumerable<IInteractiveSubItem> SubItems { get; }
        /// <summary>
        /// Repaint item to layers after current operation. Layers are not combinable. Layer None is for invisible, but active items.
        /// </summary>
        GInteractiveDrawLayer StandardDrawToLayer { get; }
        /// <summary>
        /// Repaint item to this layers after current operation. Layers are combinable. Layer None is permissible (no repaint).
        /// </summary>
        GInteractiveDrawLayer RepaintToLayers { get; set; }
        /// <summary>
        /// Repaint all items (not just me) after current events.
        /// </summary>
        bool RepaintAllItems { get; set; }
        /// <summary>
        /// Called after any interactive change
        /// </summary>
        /// <param name="e"></param>
        void StateChanged(GInteractiveChangeStateArgs e);
        /// <summary>
        /// Called to draw content of this item
        /// </summary>
        /// <param name="e"></param>
        void Draw(GInteractiveDrawArgs e);
    }
    /// <summary>
    /// Defines properties and methods for an object, which is member of parent IInteractiveItem, and is interactive on GInteractiveControl.
    /// </summary>
    public interface IInteractiveSubItem
    {
        /// <summary>
        /// Coordinates, where item is mouse-interactive, in coordinate system of parent item (IInteractiveItem).
        /// </summary>
        Rectangle RelativeActiveBounds { get; }
        /// <summary>
        /// Is item currently visible?
        /// When true, then item can be interactive and can be Drawed.
        /// </summary>
        Boolean IsVisible { get; }
        /// <summary>
        /// Hold a mouse attention.
        /// When a subitem is drawed to Interactive layer (in MouseOver, MouseDrag and in other active states), this is: above other subitem, then is advisable "hold mouse attention" for this subitem before other subitems.
        /// But when active subitem is drawed bellow other subitems, then hold mouse attention is not recommended (in example for back area of movable item, before its grips).
        /// </summary>
        bool HoldTheMouseAttention { get; }
    }
    #endregion
    #region abstract ancestor InteractiveItem
    /// <summary>
    /// Abstract ancestor for interactive items 
    /// (optionally with SubItems = any number of small areas, typically control points)
    /// </summary>
    public abstract class InteractiveItem : IInteractiveItem
    {
        #region Protected properties
        /// <summary>
        /// Parent of this item (its host). Can be null.
        /// Parent is typically an IInteractiveContainer.
        /// </summary>
        public IInteractiveItem Parent { get; set; }
        /// <summary>
        /// Bounds, where item is mouse-interactive, in coordinates of parent control (GInteractiveControl).
        /// This ActiveBounds are coordinates for MouseDrag and base coordinate system for this.SubItems (its relative bounds in IInteractiveSubItem.RelativeActiveBounds).
        /// </summary>
        public virtual Rectangle ActiveBounds { get; set; }
        /// <summary>
        /// Return true, when item is active at specified point.
        /// Point is in absolute coordinates (on Control).
        /// Item can be interactive on standard rectangle (Bounds), then test activity by method Rectangle.Contains(point).
        /// Or item can be active on multiple rectangles, or GraphicsPath...
        /// </summary>
        /// <param name="point">Point in Control coordinates, for which are test performed (typically point of mouse)</param>
        /// <returns></returns>
        protected virtual Boolean IsActiveAtPoint(Point point) { return this.ActiveBounds.Contains(point); }
        /// <summary>
        /// Interactive style
        /// </summary>
        protected virtual GInteractiveStyles Style { get { return GInteractiveStyles.StandardInteractivity; } }
        /// <summary>
        /// An array of sub-items in this item
        /// </summary>
        protected virtual IEnumerable<IInteractiveSubItem> SubItems { get { return null; } }
        /// <summary>
        /// Is item currently visible?
        /// When true, then item can be interactive and can be Drawed.
        /// Default = true.
        /// </summary>
        protected virtual Boolean IsVisible { get { return !this._IsHidden; } set { this._IsHidden = !value; } } private Boolean _IsHidden = false;
        /// <summary>
        /// Is item currently Enabled?
        /// When true, then item can be interactive (and can be Drawed).
        /// When false, hen item can NOT be interactive, but can be Drawed.
        /// Default = true.
        /// </summary>
        protected virtual Boolean IsEnabled { get { return !this._IsDisable; } set { this._IsDisable = !value; } } private Boolean _IsDisable = false;
        /// <summary>
        /// Repaint item to layers after current operation. Layers are not combinable. Layer None is for invisible, but active items.
        /// </summary>
        protected virtual GInteractiveDrawLayer StandardDrawToLayer { get { return GInteractiveDrawLayer.Standard; } }
        /// <summary>
        /// Repaint item to this layers after current operation. Layers are combinable. Layer None is permissible (no repaint).
        /// </summary>
        protected virtual GInteractiveDrawLayer RepaintToLayers { get; set; }
        /// <summary>
        /// Repaint all items (not just me) after current events.
        /// </summary>
        protected virtual bool RepaintAllItems { get; set; }
        /// <summary>
        /// Current (new) state of item (after this event, not before it).
        /// </summary>
        protected GInteractiveState CurrentState { get; set; }
        /// <summary>
        /// true when CurrentState is LeftDrag or RightDrag
        /// </summary>
        protected bool IsDragged { get { return (this.CurrentState == GInteractiveState.LeftDrag || this.CurrentState == GInteractiveState.RightDrag); } }
        /// <summary>
        /// Coordinate of mouse relative to CurrentItem.Area.Location.
        /// Can be a null.
        /// </summary>
        protected Point? CurrentMouseRelativePoint { get; set; }
        /// <summary>
        /// Called after any interactive change
        /// </summary>
        /// <param name="e"></param>
        protected abstract void StateChanged(GInteractiveChangeStateArgs e);
        /// <summary>
        /// Called to draw content of this item.
        /// Base class (InteractiveItem) in this method call but method DrawStandard() or DrawAsGhost().
        /// </summary>
        /// <param name="e"></param>
        protected virtual void Draw(GInteractiveDrawArgs e)
        {
            if (!this.IsDrawAsGhost(e))
                this.DrawStandard(e);
            else
                this.DrawAsGhost(e);
        }
        /// <summary>
        /// Draw this item in standard mode.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void DrawStandard(GInteractiveDrawArgs e)
        { }
        /// <summary>
        /// Draw this item as ghost.
        /// As ghost is drawed during Dragging, and to Standard layer.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void DrawAsGhost(GInteractiveDrawArgs e)
        { }
        /// <summary>
        /// Return true, if this item can be drawed as Ghost.
        /// Ghost is drawed to Standard layer in Drag mode (IsDragged = true).
        /// </summary>
        /// <param name="e"></param>
        /// <returns></returns>
        protected bool IsDrawAsGhost(GInteractiveDrawArgs e)
        {
            return (e.DrawLayer == GInteractiveDrawLayer.Standard && this.IsDragged);
        }
        #endregion
        #region IInteractiveArea members
        IInteractiveItem IInteractiveItem.Parent { get { return this.Parent; } }
        Rectangle IInteractiveItem.ActiveBounds { get { return this.ActiveBounds; } }
        Boolean IInteractiveItem.IsActiveAtPoint(Point point) { return this.IsActiveAtPoint(point); }
        GInteractiveStyles IInteractiveItem.Style { get { return this.Style; } }
        Boolean IInteractiveItem.IsVisible { get { return this.IsVisible; } }
        Boolean IInteractiveItem.IsEnabled { get { return this.IsEnabled; } }
        IEnumerable<IInteractiveSubItem> IInteractiveItem.SubItems { get { return this.SubItems; } }
        GInteractiveDrawLayer IInteractiveItem.StandardDrawToLayer { get { return this.StandardDrawToLayer; } }
        GInteractiveDrawLayer IInteractiveItem.RepaintToLayers { get { return this.RepaintToLayers; } set { this.RepaintToLayers = value; } }
        bool IInteractiveItem.RepaintAllItems { get { return this.RepaintAllItems; } set { this.RepaintAllItems = value; } }
        void IInteractiveItem.StateChanged(GInteractiveChangeStateArgs e)
        {
            this.CurrentState = e.CurrentState;
            this.CurrentMouseRelativePoint = e.MouseRelativePoint;
            this.StateChanged(e);
        }
        void IInteractiveItem.Draw(GInteractiveDrawArgs e)
        {
            this.Draw(e);
        }
        #endregion
    }
    #endregion
}
